home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Monster Media 1996 #15
/
Monster Media Number 15 (Monster Media)(July 1996).ISO
/
bbs_util
/
smsg_102.zip
/
SENDMSG.C
< prev
next >
Wrap
C/C++ Source or Header
|
1996-04-07
|
22KB
|
825 lines
/*
* SENDMSG: Utility to send messages for *.MSG or *.SQ? bases
*
* Created: 05/Dec/92
* Updated: 07/Apr/96
*
* Written by Pete Kvitek of JV Dialogue 1st BBS (2:5020/6) +7-095-329-2192
* Copyright (c) 1992-1996 by JV DIALOGUE. All rights reserved.
*
* History:
*
* 07/Apr/96 -- v1.02
* Ported code to MSC 6.0a to compile for DOS and OS/2 1.xx
* Updated code to use Scott's SQDEV200 instead of MSGAPI0
* Implemented more robuts ^aMSGID time stamp generation
* Changed ^aMSGID generation so that now it is not optional
* Added ^aPID kludge generation
*
* 05/Feb/93 -- v1.01
* Corrected 'grunged date' problem
* Added optional ^aMSGID generation
*
* 05/Dec/92 -- v1.00
* Originally written
*
*/
// COMPILATION NOTES:
// The SendMsg code was compiled in LARGE memory model using
// Borland's C++ and Microsoft C v6.00a, however newer
// versions may serve as well -- at least I hope so...
#ifdef __OS2__
#define INCL_NOCOMMON
#define INCL_NOPM
#define INCL_DOSFILEMGR
#define INCL_DOSMEMMGR
#define INCL_DOSINFOSEG
#define INCL_DOSPROCESS
#include <os2.h>
#define OS_2
#endif
#include <io.h>
#include <dos.h>
#include <time.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <direct.h>
// Remove the EXPENTRY definition since the Scott's API has one too.
// Both appear to be identical except for the far call specification,
// but since we're compiling in large model this is not a problem...
#undef EXPENTRY
#include "msgapi.h"
/////////////////////////////////////////////////////////////////////////////
// M o d u l e d e c l a r a t i o n s //
/////////////////////////////////////////////////////////////////////////////
// Some useful defines
#define loop while (1) // Endless loop 'till break
#define numbof(a) (sizeof(a)/sizeof(a[0])) // Number of elements
#define lengof(s) (sizeof(s) - 1) // Length of the string
#define TRUE 1
#define FALSE 0
#ifndef __OS2__
#define BOOL int
#define ULONG unsigned long
#endif
// Some compiler dependant stuff
#if defined(__TURBOC__) || defined(__BORLANDC__)
#define FN_MAXPATH MAXPATH
#define FN_MAXDRIVE MAXDRIVE
#define FN_MAXDIR MAXDIR
#define FN_MAXFILE MAXFILE
#define FN_MAXEXT MAXEXT
#elif defined(__MSC__)
#define FN_MAXPATH _MAX_PATH
#define FN_MAXDRIVE _MAX_DRIVE
#define FN_MAXDIR _MAX_DIR
#define FN_MAXFILE _MAX_FNAME
#define FN_MAXEXT _MAX_EXT
#else
#error Unknown complier
#endif
// Miscellaneous defines
#define VERSION "1.02" // Revision level
#ifndef __OS2__
#define SMSG_NAME "SendMsg" // Utility name (dos)
#else
#define SMSG_NAME "SendMsg/2" // Utility name (os/2)
#endif
#define SMSG_PID "\x01""PID: "SMSG_NAME" v"VERSION // PID kludge
#define SMSG_TEARLINE "\r--- "SMSG_NAME"\r" // Tear line
#define MSGTEXT stdin // Message text input handle
#define AUXTEXT stdout // Auxiliary text output handle
#define ISOPTION(ch) ((ch)=='-'||(ch)=='/') // Command line option prefix
// Program control flags in 'fsFlags'
#define FL_SQUISHMAIL 0x0001 // Squish mail folder
// Module variables
static char achPath[FN_MAXPATH]; // Path to mail folder
static unsigned fsFlags; // Control flags FL_
static HAREA harea; // Mail folder handle
static XMSG msg; // Message header info
// Message attribute table
static struct {
dword attr; // Attribute bit
char * psz; // Attribute name string
char ch; // Command line option char or null if none
} aMsgAttr[] = {
MSGPRIVATE, "Pvt", 'P',
MSGCRASH, "Crash", 'C',
MSGREAD, "Recv", 0,
MSGSENT, "Sent", 0,
MSGFILE, "File", 'F',
MSGFWD, "Transit", 0,
MSGORPHAN, "Orphan", 0,
MSGKILL, "Kill", 'K',
MSGLOCAL, "Local", 0,
MSGHOLD, "Hold", 'H',
MSGXX2, "Rsvd2", 0,
MSGFRQ, "Frq", 'Q',
MSGRRQ, "Rrq", 'R',
MSGCPT, "Cpt", 0,
MSGARQ, "Arq", 'A',
MSGURQ, "Urq", 'U',
MSGSCANNED, "Scn", 'S',
};
/////////////////////////////////////////////////////////////////////////////
// M i s c e l l a n e o u s s u b r o u t i n e s //
/////////////////////////////////////////////////////////////////////////////
/*
* This subroutine displays logo
*/
static void DoShowLogo(void)
{
fprintf(AUXTEXT,
"\n"
"Send Message Utility v"VERSION", "__DATE__", "__TIME__"\n"
"Written by Pete Kvitek of JV Dialogue 1st BBS, 2:5020/6\n"
"Copyright (C) 1991-1996 by JV Dialogue. All rights reserved.\n"
"\n"
);
}
/*
* This subroutine displays help
*/
static void DoShowHelp(void)
{
int iMsgAttr;
fprintf(AUXTEXT,
"Usage: SENDMSG <folder> <fromname,addr> [toname,addr] [-a<attr>] [-s<subj>]\n"
"\n"
" folder - specifies the mail folder path. If preceded\n"
" with '$' then the folder is *.SQ? type base,\n"
" otherwise it's assumed to be *.MSG style base\n"
" name,addr - specifies message from/to name and address\n"
" attr - specifies message attributes (P,H,F, etc..)\n"
" subj - specifies message subject\n"
"\n"
"If any parameter includes spaces it should be enclosed in double quotas.\n"
"The message body text file is supposed to be available through the standard\n"
"input device. Use redirection or piping characters to set it in.\n"
"\n"
"Examples: SENDMSG c:\\mail Pete,2:5020/6 220/501 -aP \"-sNew Files\" < NEWFILE.LST\n"
" echo Hi! | SENDMSG c:\\mail 2:5020/6 1.1 -aPF \"-sc:\\autoexec.bat\"\n"
);
// Show supported attributes
fprintf(AUXTEXT, "\nAttibutes: ");
for (iMsgAttr = 0; iMsgAttr < numbof(aMsgAttr); iMsgAttr++)
if (aMsgAttr[iMsgAttr].ch)
fprintf(AUXTEXT, "%c:%s ", aMsgAttr[iMsgAttr].ch,
aMsgAttr[iMsgAttr].psz);
fprintf(AUXTEXT, "\n");
}
/*
* This sub routine builds a fully qualified path
*/
BOOL BuildFullPath(char * pszDest, char * pszSrc)
{
#ifdef __MSC__
// Build fill path using comiler service and check if ok
if (_fullpath(pszDest, pszSrc, FN_MAXPATH) == NULL)
return FALSE;
#else
char achDrive[FN_MAXDRIVE];
char achDir[FN_MAXDIR];
char achFile[FN_MAXFILE];
char achExt[FN_MAXEXT];
char achCurDir[FN_MAXDIR];
int iCurDrive;
// Decompose supplied path
fnsplit(pszSrc, achDrive, achDir, achFile, achExt);
// Preserve current drive
iCurDrive = getdisk();
// Check if drive specified in the supplied path and if not,
// assume the current one
if (!achDrive[0]) {
strcpy(achDrive, "A:");
achDrive[0]+= (char) iCurDrive;
}
// Set the current drive to the requested one and check if ok.
// If failed, restore orginal drive and return error
setdisk(toupper(achDrive[0]) - 'A');
if (getdisk() != toupper(achDrive[0]) - 'A') {
setdisk(iCurDrive);
return FALSE;
}
// Preserve the current directory on the reqested drive and
// check if ok, otherwise restore initial current drive and return
strcpy (achCurDir, "\\");
if (getcurdir(0, &achCurDir[1])) {
setdisk(iCurDrive);
return FALSE;
}
// Check if directory specified and make it current
if (achDir[0]) {
// Kill trailing back slash if it's not the only character
// of the directory specification
if ((achDir[strlen(achDir) - 1] == '\\') && (strlen(achDir) > 1 ))
achDir[strlen(achDir) - 1] = '\0';
// Change to the specified directory and check if ok. If failed,
// restore the inital directory on the requested drive and change
// to the initial drive
if (chdir(achDir)) {
chdir(achCurDir);
setdisk(iCurDrive);
return FALSE;
}
}
// So we managed to make a requested directory current on the
// requested drive. Now get its full specification and this
// will be what we're after. If failed, just restore things back
strcpy(achDir, "\\");
if (getcurdir(0, &achDir[1])) {
chdir(achCurDir);
setdisk(iCurDrive);
return FALSE;
}
// Compose the fully qualified file name and restore
// the inital directory on the requested drive and change
// to the initial drive
fnmerge(pszDest, achDrive, achDir, achFile, achExt);
chdir(achCurDir);
setdisk(iCurDrive);
#endif
return TRUE;
}
/*
* This routine scans in z:n/n.p address specification
*/
static char * DoScanNetAddr(NETADDR * pnetAddr, char * psz)
{
char * pch, * pchEnd, * pchNext, ch;
// Skip through the leading spaces and fix up the end
for (pch = psz; isspace(*pch); pch++);
for (pchNext = pch; *pchNext && !isspace(*pchNext); pchNext++);
ch = *pchNext; *pchNext = '\0'; pchEnd = pch;
// Scan in the zone if any
if (*pch && !isspace(*pch) && strchr(pch, ':')) {
while (isdigit(*pchEnd)) pchEnd++;
if (*pchEnd != ':' || pch == pchEnd) return NULL;
pnetAddr->zone = atoi(pch);
pch = ++pchEnd;
if (!isdigit(*pch) || !strchr(pch, '/')) return NULL;
}
// Scan in the net if any
if (*pch && !isspace(*pch) && strchr(pch, '/')) {
while (isdigit(*pchEnd)) pchEnd++;
if (*pchEnd != '/' || pch == pchEnd) return NULL;
pnetAddr->net = atoi(pch);
pch = ++pchEnd;
if (!isdigit(*pch)) return NULL;
}
// Scan in the node if any
if (*pch && !isspace(*pch) && *pch != '.') {
while (isdigit(*pchEnd)) pchEnd++;
if (*pchEnd != '.' && !isspace(*pchEnd) && *pchEnd) return NULL;
pnetAddr->node = atoi(pch);
pch = pchEnd;
}
// Scan in the point if any
if (*pch != '.') {
if (!isspace(*pch) && *pch) return NULL;
pnetAddr->point = 0;
} else {
for (pchEnd = ++pch; isdigit(*pchEnd); pchEnd++);
if (!isspace(*pchEnd) && *pchEnd) return NULL;
pnetAddr->point = atoi(pch);
pch = pchEnd;
}
// Restore the zeroed trailing characters
*pchNext = ch;
// Check if zone or net is zero and return
return (pnetAddr->zone && pnetAddr->net) ? pchNext : NULL;
}
/*
* This subroutine scans name and network address
*/
static BOOL DoScanNameAddr(char * psz, NETADDR * pnetAddr,
char * pszName, short cchName)
{
char * pch;
short cch;
// Check if there is a name and scan it in
if (isdigit(*psz))
pch = psz;
else
if ((pch = strchr(psz, ',')) == NULL) {
return FALSE;
} else {
cch = min((short)(pch - psz), cchName);
memcpy(pszName, psz, cch);
pszName[cch] = '\0';
pch++;
}
// Scan in the network address if any
DoScanNetAddr(pnetAddr, pch);
return TRUE;
}
/*
* This subroutine scans message attribute specification
*/
static void DoScanMsgAttr(char * psz, dword * pattr)
{
short iAttr;
// Scan through all the specified message attributes
for (; *psz; psz++) {
for (iAttr = 0; toupper(*psz) != aMsgAttr[iAttr].ch; iAttr++)
if (iAttr >= numbof(aMsgAttr)) {
fprintf(AUXTEXT, "Unknown message attribute: '%s'\n", psz);
exit(EXIT_FAILURE);
}
*pattr|= aMsgAttr[iAttr].attr;
}
}
/*
* This subroutine prints out a message header
*/
static void DoShowMsgHeader(XMSG * pmsg)
{
int iAttr;
// Print out the message header
fprintf(AUXTEXT,
" To: %s, %u:%u/%u.%u\n"
"From: %s, %u:%u/%u.%u\n"
"Subj: %s\n",
pmsg->to, pmsg->dest.zone, pmsg->dest.net, pmsg->dest.node, pmsg->dest.point,
pmsg->from, pmsg->orig.zone, pmsg->orig.net, pmsg->orig.node, pmsg->orig.point,
pmsg->subj
);
// Print out the message attribute if any
if (pmsg->attr) {
fprintf(AUXTEXT, "Attr:");
for (iAttr = 0; iAttr < numbof(aMsgAttr); iAttr++)
if (pmsg->attr & aMsgAttr[iAttr].attr)
fprintf(AUXTEXT, " %s", aMsgAttr[iAttr].psz);
putc('\n', AUXTEXT);
}
}
/*
* This subroutine to process the command line parameters
*/
static void DoProcCmdLine(int cArg, char * apszArg[])
{
char * psz;
short iArg = 1;
// Display the logo screen and check if there are any
// command line parameters specified and if not, display
// help screen and quit now...
DoShowLogo();
if (cArg == 1) {
DoShowHelp();
exit(EXIT_FAILURE);
}
// Scan in the mail folder path specification and check if it's
// a *.SQ? type database
if (apszArg[iArg][0] == '$') {
psz = &apszArg[iArg][1]; fsFlags|= FL_SQUISHMAIL;
} else {
psz = &apszArg[iArg][0];
}
// Build the fully qualified path and make it upper case
if (!BuildFullPath(achPath, psz)) {
fprintf(AUXTEXT, "Invalid mail folder path: '%s'\n", psz);
exit(EXIT_FAILURE);
} else {
#ifndef __OS2__
strupr(achPath);
#endif
iArg++;
}
// Scan in the 'From' name/address
if (iArg >= cArg) {
fprintf(AUXTEXT, "Missing 'From' address/name\n");
exit(EXIT_FAILURE);
} else
if (!DoScanNameAddr(apszArg[iArg], &msg.orig, msg.from, lengof(msg.from)) ||
msg.orig.zone == 0 || msg.orig.net == 0) {
fprintf(AUXTEXT, "Invalid 'From' address: '%s'\n", apszArg[iArg]);
exit(EXIT_FAILURE);
} else {
memcpy(&msg.dest, &msg.orig, sizeof(msg.dest));
iArg++;
}
// Scan in the 'To' name/address if any
if (iArg >= cArg || ISOPTION(apszArg[iArg][0])) {
memcpy(&msg.dest, &msg.orig, sizeof(msg.dest));
} else
if (!DoScanNameAddr(apszArg[iArg], &msg.dest, msg.to, lengof(msg.to)) ||
msg.dest.zone == 0 || msg.dest.net == 0) {
fprintf(AUXTEXT, "Invalid 'To' address: '%s'\n", apszArg[iArg]);
exit(EXIT_FAILURE);
} else
iArg++;
// Process the command line options if any
for (; iArg < cArg; iArg++)
if (!ISOPTION(apszArg[iArg][0])) {
fprintf(AUXTEXT, "Invalid option: '%s'\n", apszArg[iArg]);
exit(EXIT_FAILURE);
} else
switch (tolower(apszArg[iArg][1])) {
case 's': // Message subject
strncpy(msg.subj, &apszArg[iArg][2], lengof(msg.subj));
break;
case 'a': // Message attributes
DoScanMsgAttr(&apszArg[iArg][2], &msg.attr);
break;
default: fprintf(AUXTEXT, "Unknown option: '%s'\n", apszArg[iArg]);
exit(EXIT_FAILURE);
}
// Set up default message header strings if not defined yet
if (!msg.to[0]) strcpy(msg.to, msg.attr & MSGPRIVATE ? "SysOp" : "All");
if (!msg.from[0]) strcpy(msg.from, "SysOp");
if (!msg.subj[0]) strcpy(msg.subj, "<none>");
// Explicitely set the 'Local' message attribute
msg.attr|= MSGLOCAL;
}
/*
* This subroutine returns an API error string
*/
static char * DoGetApiError(void)
{
switch (msgapierr) {
case MERR_NOMEM: return "not enough memory";
case MERR_NOENT: return "area does not exist";
case MERR_BADF: return "area is damaged";
default: return "error unknown";
}
}
/*
* This subroutine opens a mail folder
*/
static BOOL DoOpenMailFolder(void)
{
struct _minf minf;
// Initialize the Scott's API and check if ok
minf.req_version = 0;
minf.def_zone = msg.orig.zone;
if (MsgOpenApi(&minf) == -1) {
fprintf(AUXTEXT, "MsgAPI initialization failed\n");
exit(EXIT_FAILURE);
}
// Open the mail folder, lock it and check if ok
if ((harea = MsgOpenArea( achPath
, MSGAREA_NORMAL
, fsFlags & FL_SQUISHMAIL ? MSGTYPE_SQUISH : MSGTYPE_SDM
)) == NULL) {
fprintf(AUXTEXT, "Can't open folder %s%s -- %s\n",
achPath, fsFlags & FL_SQUISHMAIL ? ".SQ?" : "\\*.MSG",
DoGetApiError());
exit(EXIT_FAILURE);
}
return TRUE;
}
/*
* This subroutine closes a mail folder
*/
static BOOL DoCloseMailFolder(void)
{
// Check if opened
if (harea == NULL)
return FALSE;
// Unlock and close the netmail folder and check if ok
if (MsgCloseArea(harea) == -1) {
fprintf(AUXTEXT, "Can't close mail folder: %s\n", DoGetApiError());
exit(EXIT_FAILURE);
}
// Shut down the Scott's API
if (MsgCloseApi() == -1) {
fprintf(AUXTEXT, "MsgAPI shutdown failed\n");
exit(EXIT_FAILURE);
}
return TRUE;
}
/*
* This subroutine sets message creation date/stamp
*/
static void DoSetMsgDateTime(XMSG * pmsg)
{
time_t sec;
struct tm * ptime;
// Get the current time
sec = time(NULL); ptime = localtime(&sec);
// Set message date time stamp
pmsg->date_written.date.yr = ptime->tm_year - 80;
pmsg->date_written.date.mo = ptime->tm_mon + 1;
pmsg->date_written.date.da = ptime->tm_mday;
pmsg->date_written.time.hh = ptime->tm_hour;
pmsg->date_written.time.mm = ptime->tm_min;
pmsg->date_written.time.ss = ptime->tm_sec / 2;
}
/*
* This subroutine makes up a unique ^aMSGID stamp
*/
static ULONG DoMakeMSGIDStamp(void)
{
static ULONG lStampPrev;
ULONG lStamp, lSecs, lHund;
unsigned iLoop = 0;
#ifdef __OS2__
static BOOL fInfoSeg = FALSE;
static PGINFOSEG pgis;
static PLINFOSEG plis;
SEL selgis, sellis;
#else
union REGS regs;
#endif
// Under OS2 get pointers to the global and local info segments once
#ifdef __OS2__
if (!fInfoSeg) {
DosGetInfoSeg(&selgis, &sellis);
pgis = MAKEPGINFOSEG(selgis);
plis = MAKEPLINFOSEG(sellis);
fInfoSeg = TRUE;
}
#endif
// Make up time stamp out of number of seconds since Jan 1, 1970
// shifted 7 bits to the left OR'ed with current system clock and
// loop untill we get a new stamp
do {
#ifdef __OS2__
lSecs = (ULONG) pgis->time;
lHund = (ULONG) pgis->hundredths;
DosSleep(0);
#else
lSecs = (ULONG) time(NULL);
regs.h.ah = 0x2c; intdos(®s, ®s);
lHund = (ULONG) regs.h.dl;
#endif
lStamp = (lSecs << 7) | (lHund & 0x07f);
} while ((lStampPrev >= lStamp) && (++iLoop < 0x7fff));
// Check if we finally have unique ascending ^aMSGID kludge stamp
// ATTN: What may be reasonable action here?
if (lStampPrev >= lStamp) {
fprintf(AUXTEXT, "Can't create unique ^aMSGID lStamp=%08lx, lStampPrev=%08lx\n\n\a",
lStamp, lStampPrev);
}
return lStampPrev = lStamp;
}
/*
* This subroutine creates a new message
*/
static void DoCreateMessage(XMSG * pmsg)
{
long cchCtrl, cchBody;
short cch, ich;
char ach[512];
char * pch;
HMSG hmsg;
// Get length of the message body. Note that the
// tear line will not be written for the empty messages
if ((cchBody = filelength(fileno(MSGTEXT))) > 0)
cchBody+= sizeof(SMSG_TEARLINE);
else
cchBody = 0l;
// Open the new message and check if ok
if ((hmsg = MsgOpenMsg(harea, MOPEN_CREATE, 0)) == NULL) {
fprintf(AUXTEXT, "Can't create message\n");
exit(EXIT_FAILURE);
}
// Set the message creation date/time stamp
DoSetMsgDateTime(pmsg);
// Initizlize MSGID stamp generation logic so that we'll not get dupes
// if running successively on fast machines
DoMakeMSGIDStamp();
// Create ^aMSGID kludge string
sprintf(ach, "\x01""MSGID: %u:%u/%u.%u %08lx",
pmsg->orig.zone, pmsg->orig.net,
pmsg->orig.node, pmsg->orig.point,
DoMakeMSGIDStamp());
// Append PID kludge
strcat(ach, SMSG_PID);
// Calculate control info length including trailing zero
// required by MsgWriteMsg api
cchCtrl = strlen(ach) + 1;
// Write out the new message header and check if ok
if (MsgWriteMsg(hmsg, FALSE, pmsg, NULL, 0L, cchCtrl + cchBody,
cchCtrl, ach) == -1) {
fprintf(AUXTEXT, "Can't write message header or kludges\n");
MsgCloseMsg(hmsg);
exit(EXIT_FAILURE);
}
// Check if we have nothing to write into message body
if (!cchBody) {
// Write out the empty message body
if (MsgWriteMsg(hmsg, TRUE, NULL, "", 1, 0L, 0L, NULL) == -1) {
fprintf(AUXTEXT, "Can't write empty message body\n");
MsgCloseMsg(hmsg);
exit(EXIT_FAILURE);
}
} else {
// Write out new message text if any. Replace all \n with \r
// in order to conform FidoNet(tm) message body standards
while ((cch = fread(ach, 1, sizeof(ach), MSGTEXT)) > 0) {
for (pch = ach, ich = 0; ich < cch; pch++, ich++)
if (*pch == '\n') *pch = '\r';
if (MsgWriteMsg(hmsg, TRUE, NULL, ach, cch, 0L, 0L, NULL) == -1) {
fprintf(AUXTEXT, "Can't write message body\n");
MsgCloseMsg(hmsg);
exit(EXIT_FAILURE);
}
}
// Write out the tear line. Note that the trailing null is also
// written since we're using 'sizeof()'
if (MsgWriteMsg(hmsg, TRUE, NULL, SMSG_TEARLINE, sizeof(SMSG_TEARLINE),
0L, 0L, NULL) == -1) {
fprintf(AUXTEXT, "Can't write tear line\n");
MsgCloseMsg(hmsg);
exit(EXIT_FAILURE);
}
}
// Close the message just created and display the results
MsgCloseMsg(hmsg);
DoShowMsgHeader(&msg);
fprintf(AUXTEXT, "Body: %lu byte(s)\n"
"Mail: %s%s\n",
cchBody, achPath,
fsFlags & FL_SQUISHMAIL ? ".SQ?" : "\\*.MSG");
}
/////////////////////////////////////////////////////////////////////////////
// M a i n . . . //
/////////////////////////////////////////////////////////////////////////////
void main(int argc, char * argv[])
{
DoProcCmdLine(argc, argv);
DoOpenMailFolder();
DoCreateMessage(&msg);
DoCloseMailFolder();
exit(EXIT_SUCCESS);
}
/*
* End of SENDMSG.C
*/